<?php
/**
 * Created by PhpStorm.
 * User: tom_xia
 * Date: 2017/11/30
 * Time: 11:47
 * 分片上传类  仅仅支持单文件分片上传
 */


require_once "AbstractUpload.php";
require_once "FileConfigException.php";
class ChunkUpload extends AbstractUpload
{
    const UPLOAD_CHUNK_INDEX = 'CHUNK_UPLOAD_INDEX_';
    const UPLOAD_CHUNK_COMPLETE_INDEX = "CHUNK_UPLOAD_COMPLETE_";
    const UPLOAD_CHUNK_UID = "CHUNK_UPLOAD_FUID_";

    const UPLOAD_CHUNK_EXPIRE_IN = 86400;//分片上传 续传超时时间
    const STATUS_CHUNK_UPLOAD = 1;
    const STATUS_MERGE_UPLOAD = 2;

    protected $status = self::STATUS_CHUNK_UPLOAD;

    //分片上传配置
    protected $index;//当前是第几个分片
    protected $total;//总共多少分片
    protected $fuid;//分片文件的file uuid
    protected $savePath;//分片合并后保存的路径
    protected $totalSize;//原始文件的大小

    //无须配置的属性
    protected $redisIndex;//redis当前保存所引数,表示当前已经上传了多少个分片
    protected $uploadCompleteIndex = "";//已经完成上传的索引数
    public function __construct(array $config)
    {
        parent::__construct($config);
        //开启分片的配置
        //检查必要参数
        if(empty($config["index"]) || empty($config["total"]) ||
             empty($config["fuid"]) || empty($config["savepath"]) || empty($config["size"])){
            throw new FileUploadException("分片配置错误");
        }
        if($config["index"] < 1){
            throw new FileUploadException("分片索引不能小于1");
        }
        if($config["index"] > $config["total"]){
            throw new FileUploadException("分片索引不能大于总分片数");
        }
        $this->totalSize = intval($config["size"]);
        $this->index = $config["index"];
        $this->total = $config["total"];
        $this->fuid = $config["fuid"];
        $this->savePath = rtrim($config["savepath"],"/");
        $this->redis = $config["redis"];
    }

    /**
     * 分片上传 初始化
     */
    public static function initUpload(\Redis $redis){
        $randStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
        $randStr = str_shuffle($randStr);
        $randStr = substr($randStr,0,12);
        $f_uid = $randStr.md5($redis->incr(self::UPLOAD_INDEX));
        $f_uid = strtoupper($f_uid);
        $redis->setex(self::UPLOAD_CHUNK_UID.$f_uid,self::UPLOAD_CHUNK_EXPIRE_IN,1);
        return $f_uid;
    }

    /**
     * 检查 fuid是否过期
     * @param Redis $redis
     * @param $f_uid
     * @return bool
     */
    public static  function checkF_uid(\Redis $redis,$f_uid){
        return $redis->get(self::UPLOAD_CHUNK_UID.$f_uid) ? true : false;
    }

    /**
     * 分片 检查分片
     * @param \Redis $redis
     * @return string
     */
    public static function checkFileChunk(\Redis $redis,$f_uid,$total){
        $completeIndex = $redis->get(self::UPLOAD_CHUNK_COMPLETE_INDEX.$f_uid);
        if($completeIndex){
            $completeIndex = json_decode($completeIndex,true);
            $allIndex = range(1,$total);
            $diffIndex = array_diff($allIndex,$completeIndex);
            return implode(",",$diffIndex);
        }else{
            return implode(",",range(1,$total));
        }
    }

    /**
     * 获取分片信息
     * @return array
     */
    public  function getFileDataInfo()
    {
        $baseInfo =  ["size"=>$this->size[0], "finalFileName"=>$this->finalFileName[0]];
        if($this->status == self::STATUS_MERGE_UPLOAD){
            $baseInfo["file_path"] = $this->fileUrl[0];
        }
        if($this->status == self::STATUS_CHUNK_UPLOAD){
            return array_merge([
                "current_index"=>$this->index,
                "total_index"=>$this->total,
                "save_index"=>$this->redisIndex,
                "fuid"=>$this->fuid,
                "complete_index"=>$this->uploadCompleteIndex,
            ],$baseInfo);
        }
        return $baseInfo;
    }

    /**
     * 上传
     * @return bool
     * @throws FileUploadException
     */
    public function doupload()
    {
        $this->init();
        if(!$this->mkdir($this->uploadPath)){
            throw new FileUploadException("uploading--创建目录【{$this->uploadPath}】失败");
        }
        $destination = rtrim($this->uploadPath, "/") . "/" . $this->fuid . "_" . $this->index;
        //判断该文件是否存在，如果存在那么就不增加redis
        if (file_exists($destination)) {
            $this->redisIndex = $this->redis->get(self::UPLOAD_CHUNK_INDEX . $this->fuid);
            return true;
        }
        if($this->moveFileToSavePath($this->files[0]["tmp_name"],$destination) === false){
            throw new FileUploadException("移动文件失败");
        }
        $this->finalFileName[] = $this->fuid . "_" . $this->index;
        $isExist = $this->redis->exists(self::UPLOAD_CHUNK_INDEX.$this->fuid);
        $this->redisIndex = $this->redis->incrby(self::UPLOAD_CHUNK_INDEX.$this->fuid,1);
        if(!$isExist){
            $this->redis->expire(self::UPLOAD_CHUNK_INDEX.$this->fuid,self::UPLOAD_CHUNK_EXPIRE_IN);
        }
        //设置已经完成的索引
        $completeIndex = $this->redis->get(self::UPLOAD_CHUNK_COMPLETE_INDEX.$this->fuid);
        if($completeIndex){
            $completeIndexArr = json_decode($completeIndex,true);
            array_push($completeIndexArr,$this->index);
            $this->redis->set(self::UPLOAD_CHUNK_COMPLETE_INDEX.$this->fuid,json_encode($completeIndexArr));
            $this->uploadCompleteIndex = implode(",",$completeIndexArr);
        }else{
            $completeIndexArr = [$this->index];
            $this->redis->setEx(self::UPLOAD_CHUNK_COMPLETE_INDEX.$this->fuid,self::UPLOAD_CHUNK_EXPIRE_IN,json_encode($completeIndexArr));
            $this->uploadCompleteIndex = implode(",",$completeIndexArr);
        }
        return true;
    }
    /**
     * @desc 分片合并
     */
    public function mergeUpload()
    {
        $this->status  = self::STATUS_MERGE_UPLOAD;
        $ext =  $this->getFileExt($this->files[0]);
        //获取redis唯一id
        $index = $this->redis->incrby(self::UPLOAD_INDEX,1);
        $name = md5($this->getSaveFileKey()."_".$index) . "." . $ext;
        $savePath = $this->savePath."/".$this->getSavePathSuffix();
        if(!$this->mkdir($savePath)){
            throw new FileUploadException("merge--创建目录【{$savePath}】失败");
        }
        $desFileNameP = fopen($savePath. "/".$name, "w");
        for ($i = 1; $i <= $this->total; $i++) {
            $file = $this->uploadPath . "/".$this->fuid . "_" . $i;
            $strStream = file_get_contents($file);
            fwrite($desFileNameP, $strStream);
            unset($strStream);
            unlink($file);
            $this->redis->incrBy(self::UPLOAD_CHUNK_INDEX.$this->fuid, -1);
        }
        fclose($desFileNameP);
        $this->redis->del(self::UPLOAD_CHUNK_INDEX.$this->fuid);
        if(file_exists($savePath. "/".$name) && $this->totalSize == ($size = filesize($savePath. "/".$name)) ){
            $this->finalFileName[0] = $name;
            $this->size[0] = $size;
            $this->fileUrl[0] = rtrim($this->saveHttp,"/")."/".$this->getSavePathSuffix()."/".$name;
            return true;
        }else{
            throw new FileUploadException("合并文件失败");
        }
    }
}